package pt.utl.ist.ginger3d.domain;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import nu.xom.Element;
import nu.xom.Elements;

public class BlockFactory {
	
	public Map<String, Template> templates;
	
	private BlockTypeHandler typeHandler;
	
	public BlockFactory() {
		templates = new HashMap<String, Template>();
		typeHandler = BlockTypeHandler.getInstance();
	}

	public Template newTemplate(Element templateElem) {
		
		Map<String, String> properties = getProperties(templateElem);
		Template baseTemplate = null;
		
		if(properties.containsKey("extends")) {
			baseTemplate = templates.get(properties.get("extends"));
		}
		
		Template template = new Template(baseTemplate, templateElem.getAttributeValue("name"), getProperties(templateElem));
		addTemplateBlocks(template, templateElem.getChildElements());	
		
		templates.put(template.getName(), template);
		
		return template;
	}

	private PlaceHolderBlock applyTemplate(IBlockSet blockSet, String templateName, Map<String, String> properties, Elements subBlocks) {
				
		if(blockSet.containsBlock(templateName)) {
			
			Block block = blockSet.getBlock(templateName);
			if(!block.getClass().equals(PlaceHolderBlock.class)) {
				System.err.println("trying to apply a non-template");
				System.exit(1);
			}
			
			block = block.doCopy();
			block.updateProperties(properties, true);
			addTemplateBlocks((IBlockSet) block, subBlocks);
			
			return (PlaceHolderBlock) block;
		}
		
		return applyTemplate(templateName, properties, subBlocks);
	}
	
	private PlaceHolderBlock applyTemplate(String templateName, Map<String, String> properties, Elements subBlocks) {
		
		Template template = templates.get(templateName);
		
		PlaceHolderBlock placeHolder = new PlaceHolderBlock(templateName, template.getBlocks(), properties);
		placeHolder.updateProperties(template.getProperties(), false);
		addTemplateBlocks(placeHolder, subBlocks);
		
		return placeHolder;
	}

	private void addTemplateBlocks(IBlockSet blockSet, Elements subBlocks) {
		
		int blockCount = subBlocks.size(); 

		for(int i = 0; i < blockCount; i++) {
			
			Element blockElem = subBlocks.get(i);
			String blockName = blockElem.getAttributeValue("name");
			
			if(blockElem.getLocalName().equals("block")) {
				
				Block block = blockSet.getBlock(blockName);
				if(block == null) {
					block = new Block(blockName, blockElem.getAttributeValue("type"), getProperties(blockElem));
				}
				else {
					block = block.doCopy();
					block.updateProperties(getProperties(blockElem), true);
				}
				blockSet.addBlock(block);
			}
			else if(blockElem.getLocalName().equals("apply-template")) {
				PlaceHolderBlock block = applyTemplate(blockSet, blockElem.getAttributeValue("name"), getProperties(blockElem), blockElem.getChildElements());
				blockSet.addBlock(block);
			}
			else if(blockElem.getLocalName().equals("condition")) {
				
			}
		}
	}

	private Map<String, String> getProperties(Element blockElem) {
		
		Map<String, String> properties = new HashMap<String, String>();
		
		for(int i = 0; i < blockElem.getAttributeCount(); i++) {
			properties.put(blockElem.getAttribute(i).getLocalName(), blockElem.getAttribute(i).getValue());
		}
		return properties;
	}

	public void addType(Element typeElem) {
		
		String type = typeElem.getAttributeValue("name");
		String baseType = typeElem.getAttributeValue("base");
		Map<String, String> properties = getProperties(typeElem);
		typeHandler.addType(type, baseType, properties);
		
	}

	public List<Block> getStructure(Elements children) {

		List<Block> blocks = new ArrayList<Block>();
		for(int i = 0; i < children.size(); i++) {
			Element blockElem = children.get(i);
			PlaceHolderBlock placeHolder;
			if(blockElem.getLocalName().equals("apply-template")) {
				placeHolder = applyTemplate(blockElem.getAttributeValue("name"), getProperties(blockElem), blockElem.getChildElements()); 
				blocks.add(placeHolder);
			}
		}
		return blocks;
	}
	
}
